# -*-coding:utf-8 -*-
'''
本模块为公共方法、函数定义模块

调用方法：
1、先声明模块
    import publicMethods #公共方法、函数定义模块

2、调用时在方法前加模块前缀，如：

    filename=publicMethods.getFilenameFromPath(path) #从路径中提取文件名【不含扩展名】

'''

#■■■■■■■■■■■■■■■■■■ 调用模块
import os
from PIL import Image, ImageTk #定义图片按键用模板 ,安装：pip install Pillow -i https://pypi.douban.com/simple
import tkinter
from tkinter import ttk 
import sqlite3 #数据库模块
import chardet #查看字符编码 ,安装：pip install chardet -i https://pypi.douban.com/simple


#■■■■■■■■■■■■■■■■■■声明公共常用

from constant import STR_SELF_PATH #获取当前软件目录
from constant import STR_IMAGES_DIR #图标目录
from constant import SRE_CONVER_CODE #默认的编码'utf-8'
from constant import STR_SETUP_DB , STR_SETUP_TABLE #数据库名 、 数据表名

#■■■■■■■■■■■■■■■■■■ 定义方法

#〓〓〓〓〓〓〓 文件操作
def detectFileCode(file ):#自动检测文件编码
    file= open(file , 'rb')
    data = file.read()  #读取部分文件数据（数字越大分析越精准，但耗时也更多）
    code = chardet.detect(data)["encoding"]#根据数据分析编码
    
    if code==None or code=='': # 有时会为 None
        code=SRE_CONVER_CODE  #无法识别时使用默认编码

    return code#返回编码

    #
def getFileName(path): #从路径中返回文件名【不含扩展名】
 
    filename=os.path.split(path)[1]  # 得到含扩展名的文件全名
 
    if os.path.isdir(path): # 路径为目录时不需要拆分扩展名
        return filename
 
    # else: 非目录时（文件或者路径不存在时，按文件来处理）
    return os.path.splitext(filename)[0]
    
    #
def getFileExtension(path , case=1 ): # 从路径中返回文件扩展名【不含 "." 】
    # case=1 时强制返回小写， case=0 时照原返回，其他值 强制返回大写
 
    if os.path.isdir(path): # 目录无扩展名
        return ""
 
    extension=os.path.splitext(path)[1] # 提取扩展名（含 "."）
    if len(extension)==0: #文件无扩展名时
        return ""
    
    extension=extension[1:] # 去掉扩展名前的 "."
    
    if case==0: #照原返回
        return extension
 
    elif case==1: #强制返回小写
        return extension.lower()
 
    # else: # 强制返回大写
    return extension.upper()
    
    #
def getFileInfo(path , case=1 ): # 返回文件名和扩展名，以组元结构返回 【（文件名,扩展名）】（模拟 os.path.splitext 方法）
    return (
            getFileName( path ) ,
            getFileExtension( path , case )
            )

    #
def readTextFile(file): #打开文本文件读取内容

    try: #尝试用自动编码打开
        code=detectFileCode(file) # 自动检测文件编码
        f=open(file , 'r' , encoding=code)

        lines=f.readlines()         #读取文件内容

        f.close()#关闭文件
        
    except UnicodeDecodeError:#出错后用默认编码打开
        f=open(file , 'rb')

        lines=f.readlines()#读取文件内容

        f.close()#关闭文件

        for i in range(len(lines)):
            #忽略错误，这样能保证可以打开，但会有一些乱码
            lines[i]=lines[i].decode(SRE_CONVER_CODE , errors='ignore')#将文件的所有字符存已变量
    
    return ''.join(lines)#将字符串列表连接

    #
def saveTextFile(file , text , code=SRE_CONVER_CODE):#将 text 存到文件中，编码：code
    f= open(file , 'w',encoding=code)

    try:
        f.writelines(text)#写入文件
        f.close()

    except Exception as e:
        print(e) # 打印异常信息
        return False #基本为编码问题，如繁体存成 GB2312

    return True

    #
#〓〓〓〓〓〓〓 文本
def isChinese(uchar):#判断一个unicode是否是汉字【通用】

    inner_code=ord(uchar)
    if (uchar >= u'\u4e00' and uchar <= u'\u9fa5') or \
                                inner_code< 0x0020 or \
                                inner_code > 0x7e: #汉字及全角字符
        return True # 汉字
    else:
        return False

    #
def getTextRealLen(text):#返回最长行数的长度（可识别汉字）【通用】
    cn_count=0
    for u in text:
        if isChinese(u):
            cn_count += 2 # 计算中文字符占用的宽度

        else:
            cn_count += 1 # 计算英文字符占用的宽度

    return cn_count
    #
def ignoreCaseReplace(strFind , strReplace , strText , case=1):#支持不区分大小写的替换函数 , case=1 时区分大小写
    if case == 1: #区分大小写
        textLower = strText.lower()
        findLower = strFind.lower()

        returnText=''
        intStart=0

        strFinds=textLower.split(findLower) #按查的字符分割串

        for str_find in strFinds: 

            intEnd = len(str_find) + intStart
            returnText = returnText + strText[intStart : intEnd] + strReplace

            intStart = intEnd + len(strFind)
        
        if len(strReplace)>0: #替换为空值时，不删除未尾的替换字符串
            returnText = returnText[0 : -len(strReplace)]

        return returnText
    
    else: #不区分大小写
        return strText.replace(strFind , strReplace) 


    #
#〓〓〓〓〓〓〓 其他
def getDatasFromSQL(sql , db = STR_SETUP_DB ): # 读取数据库信息，支持 SQL 的 SELECT 语句 
    # sql 为SQL 的 select 语句 ， db 为数据库文件名 ，STR_SETUP_DB 为系统默认数据库名
 
    try:
        strSQLiteDb=os.path.join(STR_SELF_PATH , db) # 打开数据库文件 STR_SELF_PATH 为软件当前目录
        conn = sqlite3.connect(strSQLiteDb)          # 创建数据库链接
        cursor = conn.cursor()                       # 创建游标
    
        cursor.execute(sql)                          # 执行 SQL 语句
        result = cursor.fetchall()                   # 获取数据表内容

    except Exception as e:
        print("SQL:f{sql}") #打印 SQL 语句
        print(e) #打印异常信息

        result = None  # 出错时返回空
    
    cursor.close()
    conn.close()
 
    return result
 
    #
def setDatasFromSQL(sql , db = STR_SETUP_DB): # 修改数据库命令，支持 SQL 的 DELETE、UPDATE、INSERT 命令
    # sql 为SQL 的 select 语句 ， db 为数据库文件名 ，STR_SETUP_DB 为系统默认数据库名
 
    try:

        strSQLiteDb=os.path.join(STR_SELF_PATH , db) # 打开数据库文件 STR_SELF_PATH 为软件当前目录
        conn = sqlite3.connect(strSQLiteDb)          # 创建数据库链接
                
        conn.execute(sql)                            # 执行 SQL 语句
        conn.commit()                                # 应用
        
        conn.close()

    except Exception as e:
        print("SQL:f{sql}") #打印 SQL 语句
        print(e) #打印异常信息
        return False

    return True  # 成功
    #
def updateDbSetup(name , value , db = STR_SETUP_DB):#更新软件设置

    value='{}'.format(value).replace( "'" , "''" ) #避免内容中含单引号出错，转为双单引号(先强制转成字符串)

    try:
        strSQLiteDb=os.path.join(STR_SELF_PATH , db)
        conn = sqlite3.connect(strSQLiteDb)

        cursor = conn.cursor()
        cursor.execute('SELECT value FROM {} WHERE name="{}";'.format( STR_SETUP_TABLE , name )) #查看数据表中是否已存在 name 的设置

        result = cursor.fetchall()
        cursor.close()

        if len(result)>0: #存在该设置记录
            conn.execute("UPDATE {} SET value='{}' WHERE name='{}';".format( STR_SETUP_TABLE , value , name ))  # 更新
            
        else: #不存在该设置记录
            conn.execute("INSERT INTO {} (name,value) VALUES ('{}','{}')".format( STR_SETUP_TABLE , name , value )) # 插入

        conn.commit()
        conn.close()
            
        return True # 成功
    
    except Exception as e:
        print("updateDbSetup:{}".format(e))

        return False # 失败

    #
def getDatabSetup(name , default='' , db=STR_SETUP_DB):# 从数据库中读取设置值并返回，default 表示为默认设置（不能读取时的返回值）

    try:

        strSQLiteDb=os.path.join(STR_SELF_PATH , db)
        conn = sqlite3.connect(strSQLiteDb)

        cursor = conn.cursor()
        cursor.execute("SELECT value FROM {} WHERE name='{}';".format(STR_SETUP_TABLE , name))
        result = cursor.fetchall()
        cursor.close()
        
        conn.close()

        if len(result)==0:#匹配不到数据，需插入设置
            return default #返回缺省值
        
        else:#更新设置
            return result[0][0] #返回查询到的设置
        
    except Exception as e:

        print("getDatabSetup:{}".format(e))
        return default # 异常时返回默认设置

    #
def getDataTableLastId(table , db = STR_SETUP_DB):#取最后添加记录的 id 值
    res=getDatasFromSQL('SELECT MAX(id) FROM {}'.format(table) , db)

    if res[0][0]==None: #无记录时
        return 0
    
    return res[0][0]
    #
#〓〓〓〓〓〓〓 其他
def createScrollbarWidget(frame , type=0 , **kwargs): # 创建带滚动条的小部件，返回创建的小部件【支持 Treeview、Text、Listbox、Canvas】
    # frame 为上一层容器名 ，kwargs 则为各小部件支持的属性
    # type=0: treeview , =1 Text  ,=2 Listbox，其他 Canvas
 
    if type==0:
        widget=ttk.Treeview(frame , **kwargs)  # 表格

    elif type==1:
        widget=tkinter.Text(frame , **kwargs)  # 文本框

    elif type==2:
        widget=tkinter.Listbox(frame , **kwargs)  # 列表

    else:
        widget=tkinter.Canvas(frame , **kwargs)  # 画布
 
    sb=ttk.Scrollbar(frame , command=widget.yview)
    sb.pack(side=tkinter.RIGHT , fill=tkinter.Y) # 滚动条
    widget.pack(side=tkinter.LEFT , fill=tkinter.BOTH , expand=True)  # 小部件
    widget.config(yscrollcommand=sb.set)
  
    return widget

    #


    #
def loadImage(image):  #加载图片（图标），注意：图片必须保存在 images 目录中

    try:
        file=os.path.join( os.path.join( STR_SELF_PATH , STR_IMAGES_DIR) , image)  #合拼图片路径
        return ImageTk.PhotoImage(Image.open(file)) #返回图像

    except Exception as e:
        print(e)
    
    return None

    #
